#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>

#include "serializable.h"
#include "raid.h"
#include "sincronizacion.h"

int socketReciveHD(Planificadores* ps, int sock, int32_t sector, void* data, int len){
	pthread_mutex_lock(&s_pop_HD[2]);

	//busco el disco
	Disco* hd=NULL;
	if (ps->length>0){
		tNodoHD* n;
		HDlista_getfirst(ps, &n);
		while(n!=NULL){
			if(n->Dato.Socket==sock){
				hd=&n->Dato;
				break;
			}
			HDlista_getNext(ps, &n);
		}
	}

	if (hd==NULL) {
		printf("ERROR no se encontro disco en lista de hds para rec el sector");
		free(data);
		return 0;
	}

	//rrecorro los pedidos enviados que contengan este sector
	if (hd->Pedidos->length >0){
		tNodo* n;
		Pedido* p;
		n=lista_getfirst(hd->Pedidos);
		while(n!=NULL){
			p=n->Dato;
			if (p->Sector==sector){
				//elimino el pedido
				pthread_mutex_lock(&s_pop_Pedido);
				p=lista_popNodo((hd->Pedidos), n);
				pthread_mutex_unlock(&s_pop_Pedido);

				//p->statusPD=2; //0nada, 1enviado, 2recivido.

				if (p->Owner==0){printf ("RTA PARA FS ");}else{printf ("RTA PARA HD ");}
				if (p->Tipo==cREAD){printf ("RTA READ, hd sector: %i %i \n",hd->id, sector);}else{printf ("RTA WRITE, hd sector: %i %i \n",hd->id, sector);}

				if (p->Owner==0){//si el dueño es el FS, pùede ser el disoc de sicncro
					p->length=len;
					p->Info=data;
					SocketSendFS(p);
					free(p->Info);
					free(p);
				}else{
				   if (p->Tipo==cREAD){
					   p->length=len;
					   p->Info=data;
					   //free(p->Info);
					   //p->Info=NULL;
					   //p->length=0;
					   EscribirDiscoSincro(ps, p);
				   }else{
					   //libero memo del anterior
					   free(p->Info);
					   p->length=len;
					   p->Info=data;
					   //nada, OK, escribio el pedido en el DISCO
					   free(p->Info);
					   RevisarDiscoSincro(ps, p->Sector);
					   free(p);
				   }
				}
				//elimino el pedido
				/*
				pthread_mutex_lock(&s_pop_Pedido);
				tDato d=lista_popNodo((hd->Pedidos), n);
				if ((d.Owner!=0)&&(d.Tipo!=cREAD)){
					free(d.Info);
				}
				pthread_mutex_unlock(&s_pop_Pedido);
				*/
				//pthread_mutex_unlock(&n->Dato.s_del);
				pthread_mutex_unlock(&s_pop_HD[2]);
				//break;
				return 0;
			}

			n=lista_getNext(hd->Pedidos, n);
		}
	}
	pthread_mutex_unlock(&s_pop_HD[2]);
	printf("ERROR no se encontro el pedido en la lista");
	free(data);

	return 0;
}

/*
 * envia la rta al FS traves de la interface
 */
//int SocketSendFS(int sock, int32_t sector, buffer* data, int len, int type){
int SocketSendFS(Pedido* p){
	//p->statusPD=1;
	//cambio el estado del disco y del pedido.
	Message aHandshakeMsg;
	Serializable_t aSerializable;
	int32_t enviados;

	aHandshakeMsg.type= 2;//write
	if (p->Tipo==cREAD){
		aHandshakeMsg.type= 1;//lectura
	}
	aHandshakeMsg.sectorRequest = p->Sector;
	aHandshakeMsg.contentSize= p->length;
	aHandshakeMsg.content= p->Info;
	Serialize_Message(&aSerializable,aHandshakeMsg);

	enviados=send(p->sock, aSerializable.Data,aSerializable.Size,MSG_CONFIRM);

	//printf("PPD-RAIDenviados: %i\n", enviados);
	printf ("SEND Tipo %i  FS, SEC, L: %i %i %i \n",aHandshakeMsg.type, p->sock, p->Sector, enviados);
	//Deserialize_Message(&aHandshakeMsg, aSerializable);
	Serializable_Free(&aSerializable);

	return enviados;
}

/*
 * envia pedido de i/o directamente al disco a traves de la interface
 */
//void procesarDisco(Disco *d, Pedido* p){
int SocketSendHD(Disco *d, Pedido* p){

	//p->statusPD=1;
	//cambio el estado del disco y del pedido.
	Message aHandshakeMsg;
	Serializable_t aSerializable;
	int32_t enviados;

	//sleep(1);
	if (p->Tipo==cREAD){
		//printf ("SEND SOCKET READ HD: %i %i \n",d->id, p->Sector);

		//pido el sector 10
		aHandshakeMsg.type= 1;//lectura
		aHandshakeMsg.sectorRequest = p->Sector;

		/*
		aHandshakeMsg.contentSize= 512;
		buffer* data=malloc(512);
		memset(data,0,512);
		aHandshakeMsg.content= data;
		 */
		aHandshakeMsg.contentSize= 0;
		aHandshakeMsg.content= NULL;

		Serialize_Message(&aSerializable,aHandshakeMsg);

		enviados=send(d->Socket, aSerializable.Data,aSerializable.Size,MSG_CONFIRM);
		printf ("SEND SOCKET READ HD, SEC, L: %i %i %i \n",d->id, p->Sector, enviados);
		//free(data);
		Serializable_Free(&aSerializable);
	}else{
		//printf ("SEND SOCKET WRITE HD: %i %i \n",d->id, p->Sector);

		//escribo el sector 10
		aHandshakeMsg.type= 2;//write
		aHandshakeMsg.sectorRequest = p->Sector;
		aHandshakeMsg.contentSize= p->length;
		aHandshakeMsg.content= p->Info;
		Serialize_Message(&aSerializable,aHandshakeMsg);

		enviados=send(d->Socket, aSerializable.Data,aSerializable.Size,MSG_CONFIRM);
		printf ("SEND WRITE  HD, SEC, L: %i %i %i \n",d->id, p->Sector, enviados);
		//Deserialize_Message(&aHandshakeMsg, aSerializable);
		Serializable_Free(&aSerializable);


	}
	if (enviados >0){
		p->statusPD=1;
	}
	return enviados;
}

//n hilos de Mirada, 1 x de cada Planificador
void* procesarPedidosHD(void* vhd){
  Disco* hd=(Disco*)vhd;
  while(1){
	pthread_mutex_lock(&s_pop_HD[3]);

	if (hd->Desconectado){
		printf (" hilo exit procesarPedidosHD %i %i \n",hd->id, hd->Socket);
		pthread_mutex_unlock(&s_pop_HD[3]);
		pthread_exit(NULL);
		//return procesarPedidosHD;
	}

	if ((hd->Socket>0) && (hd->Pedidos->length > 0)){
    	//rrecorro los pedidos no enviados al hd y los envio.

    	tNodo* n;
    	Pedido* p;

    	int res;
    	pthread_mutex_lock(&s_pop_Pedido);
		n=lista_getfirst(hd->Pedidos);
		while(n!=NULL){
			//pthread_mutex_lock(&n->Dato.s_del);
			p=n->Dato;
			if (p->statusPD==0){//no enviados
				//wait(s_pedidosHD[])
				res=SocketSendHD(hd, p);
				if (res==-1){
					//cierro el hilo
					printf (" hilo exit procesarPedidosHD(resp send) %i\n",hd->id);
					//pthread_mutex_unlock(&n->Dato.s_del);

					pthread_mutex_unlock(&s_pop_Pedido);
					pthread_mutex_unlock(&s_pop_HD[3]);
					pthread_exit(NULL);
					//return procesarPedidosHD;
				}

			}
			//tNodo* nant=n;
			n=lista_getNext(hd->Pedidos, n);
			//pthread_mutex_lock(&nant->Dato.s_del);
		}
		pthread_mutex_unlock(&s_pop_Pedido);
    }
	pthread_mutex_unlock(&s_pop_HD[3]);

  }
  return procesarPedidosHD;
}

/*
 * me llego la lectura del pedido a un disco para la sincronizacion
 * ahora escribo en ese disco.
*/
int EscribirDiscoSincro(Planificadores* ps, Pedido* p){
  //busco los discos que esten sincronizando y a cada uno le meto la rta.

	if (ps->length>0){
		tNodoHD* n;
		HDlista_getfirst(ps, &n);
		while(n!=NULL){
			if(n->Dato.Sincornizando){
					p->Tipo=cWRITE;
					p->statusPD=0;
					p->Owner=1;

				printf ("pedido de WRITE Sincro hd, sector, LEN: %i %i %i\n",n->Dato.id, p->Sector, p->length);
				lista_push((n->Dato.Pedidos), p);
				//signal(s_pedidosHD[])
			}
			HDlista_getNext(ps, &n);
		}
	}
	return 0;

}

int RevisarDiscoSincro(Planificadores* ps, int32_t sector){
  //busco los discos que esten sincronizando y a cada uno le meto la rta.

	//printf (" verificando si la sincro esta completa csincro% \n");

	if (ps->length>0){
		tNodoHD* n;
		HDlista_getfirst(ps, &n);
		while(n!=NULL){
			if(n->Dato.Sincornizando){
				n->Dato.CantSincro++;

				printf (" verificando si la sincro esta completa csincro%i , tsincro%i\n", n->Dato.CantSincro, n->Dato.CantTotalSincro);
				if (n->Dato.Sector_Hasta_Parcial==n->Dato.Sector_Fin){
					if (n->Dato.CantSincro >= n->Dato.CantTotalSincro){
						n->Dato.Sincornizando=0;
						n->Dato.Sector_Hasta_Sincronizado=n->Dato.Sector_Fin;
						printf ("finalizacion sincronizacion hd: %i\n",n->Dato.id);
					}
				}else{
					if (n->Dato.CantSincro >= n->Dato.CantTotalSincro){
						n->Dato.Sector_Hasta_Sincronizado=n->Dato.Sector_Hasta_Parcial;
						printf ("finalizacion de parte de sincronizacion, hd: %i\n",n->Dato.id);

						tSincro* ts;
						ts=(tSincro* ) malloc(sizeof(tSincro));
						ts->pHD=&n->Dato;
						ts->pPlanif=ps;
						//SincronizarDisco(ts);
						//free(ts->pHD->hiloSincro);
						int iret2 = pthread_create( &(ts->pHD->hiloSincro), NULL, SincronizarDisco, (void*)ts);
						if (iret2==0){
							//todobien
							printf("hilo sincro creado");
						}else{
							//hilo no creado.
							printf("error al sincro crear hilo");
						}
					}
				}
			}
			HDlista_getNext(ps, &n);
		}
	}
	return 0;

}


/*
 * hilo q sincroniza un disco, tomando como origen cualquier hd, pero destino el q es pasado.
   encola los pedidos de lecturas a los discos para la sincronizacion
 */
void* SincronizarDisco(void* vts){
	//Planificadores* ps, Disco* hd

	tSincro* ts= (tSincro*) vts;
	Planificadores* ps=ts->pPlanif;
	Disco* hd=ts->pHD;

	//Armo todos los pedidos de lectura/escritura
	if ((hd->Socket>0)&&(ps->length>1)){

		hd->Sincornizando=1;
		printf ("sincronizando parte de hd: %i\n",hd->id);

		int32_t si;//=hd0->Sector_Ini;
		int32_t sf;//=hd0->Sector_Fin;


		int avance=5000;//5000;
		si=hd->Sector_Hasta_Parcial+1;
		hd->Sector_Hasta_Parcial=hd->Sector_Hasta_Parcial+avance;
		if (hd->Sector_Hasta_Parcial>hd->Sector_Fin)
			hd->Sector_Hasta_Parcial=hd->Sector_Fin;
		sf=hd->Sector_Hasta_Parcial;

		if (si>sf){
			hd->Sincornizando=0;
			printf("finalizacion return de sincronizacion");
			free(vts);
			return SincronizarDisco;
		}

		hd->CantSincro=0;
		hd->CantTotalSincro=sf-si+1;

		int32_t i;
		for (i=si; i<=sf; i++){

			//Armo el pedido
				Pedido* p=(Pedido*) malloc(sizeof(Pedido));
					p->length=0;
					p->Info=NULL;
					p->Sector=i;
					p->Tipo=cREAD;
					p->statusPD=0;
			        p->Owner=1;

			    //EnviarLecturaPlanif(ps, &p);
			        //busco algun planificador, con algun criterio
			        //podria ir encolando de a n Pedidos.
			    printf ("pedido de sincronizacion READ hd, sector: %c %i\n",'X', p->Sector);

			    lista_push(&Pedidos, p);
				//lista_push((hdmin->Pedidos), p);
				//signal(s_pedidosHD[])
		};

	}
	free(vts);
	return SincronizarDisco;
}

/*
 * Agrego Disco al Lista de Disco y Sincornizo dicho disco, creo un hilo por cada Disco New.
 */
int ConectarDisco(Planificadores* ps, Disco hd){
    int sincro=1;
	if (ps->length==0){
		sincro=0;
		printf ("PROCESO RAID EN FUNCIONAMIENTO\n");
	}

	printf ("conectando hd %i \n",hd.id);

	HDlista_push(ps, hd);

	tSincro* ts;
	ts=(tSincro* ) malloc(sizeof(tSincro));
	ts->pHD=&ps->Actual->Dato;
	ts->pPlanif=ps;

	//pthread_mutex_init(&ts->pHD->s_Desconexion,NULL);

	if ((sincro)&&(strcmp((char *) archPPD->Sincroniza, "SI") == 0) ){
		printf ("COMIENZO SINCRONIZACION HD %i \n",ts->pHD->id);
		//SincronizarDisco(ts);
		int iret2 = pthread_create( &(ts->pHD->hiloSincro), NULL, SincronizarDisco, (void*)ts);
    	if (iret2==0){
    		//todobien
    		printf("hilo sincro creado");
		}else{
			//hilo no creado.
			printf("error al sincro crear hilo");
		}
    }
	//pthread_join(hFS,NULL);

	//creo un hilo por cada disco
	int iret1 = pthread_create( &(ts->pHD->hilo), NULL, procesarPedidosHD, (void*)(ts->pHD));
	if (iret1==0){
		//todobien
		printf("hilo creado disco");
	}else{
		//hilo no creado.
		printf("error al crear hilo disco");
	}

	//free(ts);

	return 0;
}

